using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;
using static LightCAD.MathLib.MathEx;

namespace LightCAD.Three
{
    public class Sprite : Object3D, IMaterialObject, IGeometry, ISolid
    {
        #region scope properties or methods

        private static BufferGeometry _geometry;
        //private static Vector3 _intersectPoint = new Vector3();
        //private static Vector3 _worldScale = new Vector3();
        //private static Vector3 _mvPosition = new Vector3();
        //private static Vector2 _alignedPosition = new Vector2();
        //private static Vector2 _rotatedPosition = new Vector2();
        //private static Matrix4 _viewWorldMatrix = new Matrix4();
        //private static Vector3 _vA = new Vector3();
        //private static Vector3 _vB = new Vector3();
        //private static Vector3 _vC = new Vector3();
        //private static Vector2 _uvA = new Vector2();
        //private static Vector2 _uvB = new Vector2();
        //private static Vector2 _uvC = new Vector2();
        private static SpriteContext getContext() =>
            ThreeThreadContext.GetCurrThreadContext().SpriteCtx;
        private static void transformVertex(Vector3 vertexPosition, Vector3 mvPosition, Vector2 center, Vector3 scale, double sin = NaN, double cos = NaN)
        {
            var ctx = getContext();
            var _alignedPosition = ctx._alignedPosition;
            var _rotatedPosition = ctx._rotatedPosition;
            var _viewWorldMatrix = ctx._viewWorldMatrix;

            // compute position in camera space
            _alignedPosition.SubVectors(vertexPosition.ToVector2(), center).AddScalar(0.5).Multiply(scale.ToVector2());

            // to check if rotation is not zero
            if (!IsNaN(sin))
            {

                _rotatedPosition.X = (cos * _alignedPosition.X) - (sin * _alignedPosition.Y);
                _rotatedPosition.Y = (sin * _alignedPosition.X) + (cos * _alignedPosition.Y);

            }
            else
            {

                _rotatedPosition.Copy(_alignedPosition);

            }


            vertexPosition.Copy(mvPosition);
            vertexPosition.X += _rotatedPosition.X;
            vertexPosition.Y += _rotatedPosition.Y;

            // transform to world space
            vertexPosition.ApplyMatrix4(_viewWorldMatrix);

        }

        #endregion

        #region Properties

        public BufferGeometry geometry;
        public SpriteMaterial material;
        public Vector2 center;

        #endregion

        #region constructor
        public Sprite(SpriteMaterial material = null)
        {
            this.type = "Sprite";
            if (_geometry == null)
            {
                _geometry = new BufferGeometry();
                var float32Array = new double[]
                {
                        -0.5, -0.5, 0, 0, 0,
                        0.5, -0.5, 0, 1, 0,
                        0.5, 0.5, 0, 1, 1,
                        -0.5, 0.5, 0, 0, 1
                };
                var interleavedBuffer = new InterleavedBuffer(float32Array, 5);
                _geometry.setIndex(new int[] { 0, 1, 2, 0, 2, 3 });
                _geometry.setAttribute("position", new InterleavedBufferAttribute(interleavedBuffer, 3, 0, false));
                _geometry.setAttribute("uv", new InterleavedBufferAttribute(interleavedBuffer, 2, 3, false));
            }
            this.geometry = _geometry;
            this.material = material ?? new SpriteMaterial();
            this.center = new Vector2(0.5, 0.5);
        }
        #endregion

        #region methods
        public override void raycast(Raycaster raycaster, ListEx<Raycaster.Intersection> intersects = null, object vars = null)
        {
            if (raycaster.camera == null)
            {
                console.error("THREE.Sprite: 'Raycaster.camera' needs to be set in order to raycast against sprites.");
                return;
            }
            var ctx = getContext();
            var _worldScale = ctx._worldScale;
            var _viewWorldMatrix = ctx._viewWorldMatrix;
            var _mvPosition = ctx._mvPosition;
            var _vA = ctx._vA;
            var _vB = ctx._vB;
            var _vC = ctx._vC;
            var _uvA = ctx._uvA;
            var _uvB = ctx._uvB;
            var _uvC = ctx._uvC;
            var _intersectPoint = ctx._intersectPoint;

            _worldScale.SetFromMatrixScale(this.matrixWorld);
            _viewWorldMatrix.Copy(raycaster.camera.matrixWorld);
            this.modelViewMatrix.MultiplyMatrices(raycaster.camera.matrixWorldInverse, this.matrixWorld);
            _mvPosition.SetFromMatrixPosition(this.modelViewMatrix);
            if (raycaster.camera is PerspectiveCamera && !this.material.sizeAttenuation)
            {
                _worldScale.MulScalar(-_mvPosition.Z);
            }
            var rotation = this.material.rotation;
            double sin = NaN, cos = NaN;
            if (rotation != 0)
            {
                cos = Math.Cos(rotation);
                sin = Math.Sin(rotation);
            }
            var center = this.center;
            transformVertex(_vA.Set(-0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
            transformVertex(_vB.Set(0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
            transformVertex(_vC.Set(0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);
            _uvA.Set(0, 0);
            _uvB.Set(1, 0);
            _uvC.Set(1, 1);
            // check first triangle
            var intersect = raycaster.ray.IntersectTriangle(_vA, _vB, _vC, false, _intersectPoint);
            if (intersect == null)
            {
                // check second triangle
                transformVertex(_vB.Set(-0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);
                _uvB.Set(0, 1);
                intersect = raycaster.ray.IntersectTriangle(_vA, _vC, _vB, false, _intersectPoint);
                if (intersect == null)
                {
                    return;
                }
            }
            var distance = raycaster.ray.Origin.DistanceTo(_intersectPoint);
            if (distance < raycaster.near || distance > raycaster.far) return;
            intersects.Push(new Raycaster.Intersection()
            {
                distance = distance,
                point = _intersectPoint.Clone(),
                uv = Triangle.GetInterpolation(_intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2()),
                face = null,
                target = this
            });
        }
        public Sprite copy(Sprite source, bool recursive)
        {
            base.copy(source, recursive);
            if (source.center != null) this.center.Copy(source.center);
            this.material = source.material;
            return this;
        }
        public override Object3D copy(Object3D source, bool recursive = true)
        {
            return copy(source as Sprite, recursive);
        }
        public override Object3D clone(bool recursive = true)
        {
            return new Sprite(null).copy(this, recursive);
        }
        public Material getMaterial()
        {
            return this.material;
        }

        public void setMaterial(Material material)
        {
            this.material = material as SpriteMaterial;
        }

        public ListEx<Material> getMaterials()
        {
            return new ListEx<Material> { this.material };
        }

        public void setMaterials(ListEx<Material> materials)
        {
            this.setMaterial(materials[0]);
        }

        public bool isMultiMaterial()
        {
            return false;
        }

        public BufferGeometry getGeometry()
        {
            return geometry;
        }

        public void setGeometry(BufferGeometry geo)
        {
            this.geometry = geo;
        }
        #endregion

    }
}
